#!/bin/dash
#
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# Helper function to dump VPD RO/RW content into /var/vpd_2.0.txt.
#
# Used in:
#  + OOBE reads this log file for the default locale setting.
#  + chrome://system reads filtered file.
#

#
# Make file access-able by root only.
# $1: name of file to change ownership/permissions to.
#
set_conservative_perm() {
  chown root:root "$1"
  chmod go-stwx "$1"
}

#
# Change file permissions so it is world readable.
# $1: name of file to change ownership/permissions to.
#
set_world_readable() {
  set_conservative_perm "$1"
  chmod ugo+r "$1"
}

#
# Set a directory as world enterable.
# $1: directory path to set world enterable.
#
set_world_enterable() {
  local dir="$1"
  set_conservative_perm "${dir}"
  chmod ugo+x "${dir}"
}


umask 0077  # Conservative strategy. Allow root-only first. Open later.

# A temporary file used for caching the results of flashrom across subsequent
# invocations of the vpd utility within this script.
BIOS_TMP=$(mktemp)

# The unencrypted directory to store VPD cache files. VPD may run when encrypted
# partition is unavailable. Also, accessing unencrypted partition is faster.
CACHE_DIR="/mnt/stateful_partition/unencrypted/cache/vpd"

# mktemp depends on existence of ${CACHE_DIR}.
mkdir -p "${CACHE_DIR}"
set_world_enterable "${CACHE_DIR}"

# Files for temporary and final caching of full VPD data. Note that the
# temporary file is under ${CACHE_DIR} to ensure that renaming (mv) is atomic.
CACHE_TMP=$(mktemp --tmpdir=${CACHE_DIR} full-v2.txt.tmp.XXXXXXXXXX)
CACHE_FILE="${CACHE_DIR}/full-v2.txt"
CACHE_LINK="/var/cache/vpd/full-v2.txt"

# Location for storing cached ECHO coupon codes.
ECHO_COUPON_TMP=$(mktemp --tmpdir=${CACHE_DIR} vpd_echo.txt.tmp.XXXXXXXXXX)
ECHO_COUPON_FILE="${CACHE_DIR}/echo/vpd_echo.txt"
ECHO_COUPON_LINK="/var/cache/echo/vpd_echo.txt"

# A space delimited list of old VPD cache files, which will be removed as a
# cleanup measure. Please be sure to update this list as the cache filename
# changes between versions of this script!
OLD_CACHE_FILES="/var/cache/vpd/full.cache /var/cache/offers/vpd_echo.txt \
                 /var/cache/vpd/full-v2.cache"

# Files for temporary and final storage of filtered VPD data. Note that the
# temporary file is under ${CACHE_DIR} to ensure that renaming (mv) is atomic.
FILTERED_TMP=$(mktemp --tmpdir=${CACHE_DIR} filtered.txt.tmp.XXXXXXXXXX)
FILTERED_FILE="${CACHE_DIR}/filtered.txt"
FILTERED_LINK="/var/log/vpd_2.0.txt"

# Remove the temp file, which has full data if the script terminates early.
trap "rm -f ${BIOS_TMP} ${FILTERED_TMP} ${CACHE_TMP} ${ECHO_COUPON_TMP}" EXIT

# Load shflags, define script flags.
. /usr/share/misc/shflags

DEFINE_boolean "clean" ${FLAGS_FALSE} \
  "Clean VPD cache and output files, then quit."
DEFINE_boolean "force" ${FLAGS_FALSE} \
  "Force regeneration of VPD cache and output files."
DEFINE_boolean "full" ${FLAGS_FALSE} \
  "Generate full output, without filtering."
DEFINE_boolean "stdout" ${FLAGS_FALSE} \
  "Dump VPD to standard output, instead of a file."
DEFINE_boolean "debug" ${FLAGS_FALSE} \
  "Debug mode."


# Generate a sed filter for VPD output depending on the provided flag.
#
# $1: Boolean flag: permit all output (true) or apply whitelist (false).
# $2, $3, ..: (Optional) Each parameter is one whitelisted key.
generate_sed_filter() {
  local output=''
  local all_output="$1"
  shift
  if [ "$all_output" -eq "${FLAGS_FALSE}" ]; then
    for field in $*; do
      output="${output}"'/^"'"${field}"'"=".*"$/p;'
    done
    output="${output}"'/^.*/d;'
  fi

  echo "${output}"
}

# Perform an atomic file move that is also safe on unclean shutdown. To
# accomplish this, the source file is synced to disk. This avoids the problem
# of the meta data for the rename being visible on disk while the data blocks
# have not or not entirely been flushed to disk due to a crash.
atomic_move() {
  local source="$1"
  local dest="$2"

  dd if=/dev/null of="${source}" conv=notrunc,fdatasync
  mv -f "${source}" "${dest}"
}

# Generate the coupon code file containing cached VPD ECHO attributes.
generate_echo_codes() {
  local echo_code_keys="
    ubind_attribute
    gbind_attribute"
  local coupon_dir="$(dirname ${ECHO_COUPON_FILE})"
  local link_dir="$(dirname ${ECHO_COUPON_LINK})"

  mkdir -p "${coupon_dir}" "${link_dir}"
  if [ -f "${ECHO_COUPON_FILE}" ]; then
    return
  fi

  # If the file exists but isn't regular, it will be removed first.
  rm -f "${ECHO_COUPON_FILE}"
  rm -f "${ECHO_COUPON_LINK}"

  sed -e "$(generate_sed_filter ${FLAGS_FALSE} ${echo_code_keys})" \
    < "${CACHE_FILE}" > "${ECHO_COUPON_TMP}"
  atomic_move "${ECHO_COUPON_TMP}" "${ECHO_COUPON_FILE}"
  set_conservative_perm "${ECHO_COUPON_FILE}"

  # Since chrome needs access to this, the file is readable by group chronos.
  # Note: It should NOT be world readable.
  # TODO(gauravsh): Broker this via debugd. http://crosbug.com/28285
  chown -R root:chronos "${coupon_dir}" "${link_dir}"
  chmod -R g+rx "${coupon_dir}" "${link_dir}"
  chmod g-x "${ECHO_COUPON_FILE}"
}

# Invoke the VPD utility for generating full VPD content.
#
# $1: BIOS filename
# $2: partition name
# $3: file name to append output
generate_full_text() {
  (vpd -f "$1" -i "$2" -l || echo "# $2 execute error.") >> "$3"
}

# Migrate the legacy file under encrypted partition to be a symlink pointing to
# the target file under unencrypted partition.
#
# After call, the legacy file becomes symlink (either be pointed to target, or
# be moved to target).
migrate() {
  local legacy="$1"
  local target="$2"

  if [ -L "${legacy}" ]; then
    return
  elif [ ! -e "${legacy}" ]; then
    echo -n # create symlink at end of function.
  elif [ -f "${legacy}" ]; then
    if [ -e "${target}" ]; then
      rm -f "${legacy}"
    else
      # To get an atomic move, the legacy file is first copied to a temporary
      # file on the destination file system which is then moved into place.
      mkdir -p "$(dirname ${target})"
      local target_tmp=$(mktemp --tmpdir="$(dirname ${target})" \
          "$(basename ${target}).tmp.XXXXXXXXXX")
      mv -f "${legacy}" "${target_tmp}"
      atomic_move "${target_tmp}" "${target}"
    fi
  else
    echo "# The type of legacy file ${legacy} cannot be migrated."
    exit 1
  fi
  mkdir -p "$(dirname ${legacy})"
  ln -sf "${target}" "${legacy}"
}


#
# main()
#

# Parse arguments.
FLAGS "$@"
eval set -- "${FLAGS_ARGV}"
set -e

if [ "${FLAGS_debug}" -eq ${FLAGS_TRUE} ]; then
  debug_log="/tmp/dump_vpd_log.debug"
fi

# Cleanup: remove old versions of the VPD cache file; this ensures that we
# don't have unused VPD data lying around, which takes unnecessary space, might
# lead to stale VPD log extraction (in case of a script version rollback), and
# is perceived as a potential security breach.
for f in ${OLD_CACHE_FILES}; do
  rm -f "${f}"
done

# Remove output files if --clean or --force flagged.
if [ "${FLAGS_clean}" -eq ${FLAGS_TRUE} -o \
     "${FLAGS_force}" -eq ${FLAGS_TRUE} ]; then
  rm -f "${FILTERED_FILE}" "${CACHE_FILE}" "${ECHO_COUPON_FILE}" \
        "${FILTERED_LINK}" "${CACHE_LINK}"

  # If --clean was flagged, we're done.
  if [ "${FLAGS_clean}" -eq ${FLAGS_TRUE} ]; then
    exit 0
  fi
fi

if [ "${FLAGS_stdout}" -eq ${FLAGS_TRUE} ]; then
  FILTERED_TMP=/dev/stdout
elif [ "${FLAGS_full}" -eq ${FLAGS_TRUE} ]; then
  # --full must only be used with --stdout, to prevent accidental dumping of
  # sensitive VPD info into a world-readable file. To be used as follows:
  #
  #   dump_vpd_log --full --stdout  > a_root_readable_file
  #
  echo "You specified --full without --stdout, aborting."
  exit 1
fi

# If the cache file is missing, generate it.
if [ ! -f "${CACHE_FILE}" ]; then
  # If the file exists, but was not regular.
  rm -f "${CACHE_FILE}"
  args_partial="-p host -i FMAP -i RO_VPD -i RW_VPD -r ${BIOS_TMP}"
  args_whole="-p host -r ${BIOS_TMP}"
  if [ -n "${debug_log}" ]; then
    echo "-------------------" "$(date)" >> "${debug_log}"
    flashrom ${args_partial} -V -V -V >> "${debug_log}" 2>&1 ||
      flashrom ${args_whole} -V -V -V >> "${debug_log}" 2>&1 ||
      exit 1
  else
    flashrom ${args_partial} ||
      flashrom ${args_whole} ||
      exit 1
  fi

  generate_full_text "${BIOS_TMP}" "RO_VPD" "${CACHE_TMP}"
  generate_full_text "${BIOS_TMP}" "RW_VPD" "${CACHE_TMP}"
  atomic_move "${CACHE_TMP}" "${CACHE_FILE}"

  # Remove existing filtered output file, forcing it to be regenerated.
  rm -f "${FILTERED_FILE}"
fi

# If the ECHO coupon cache is missing, generate it.
generate_echo_codes

# Filter the full VPD output.
if [ "${FLAGS_stdout}" -eq ${FLAGS_TRUE} -o \
     ! -f "${FILTERED_FILE}" ]; then
  WHITELIST="
    ActivateDate
    customization_id
    initial_locale
    initial_timezone
    keyboard_layout
    model_name
    panel_backlight_max_nits
    region
    rlz_brand_code
    sku_number"
  sed -e "$(generate_sed_filter ${FLAGS_full} ${WHITELIST})" < "${CACHE_FILE}" \
    > "${FILTERED_TMP}"

  # Rename temporary into permanent output if --stdout was not used.
  if [ "${FLAGS_stdout}" -eq ${FLAGS_FALSE} ]; then
    set_world_readable "${FILTERED_TMP}"
    atomic_move "${FILTERED_TMP}" "${FILTERED_FILE}"
  fi
fi

# Create symlinks if needed.
migrate "${FILTERED_LINK}" "${FILTERED_FILE}"
migrate "${CACHE_LINK}" "${CACHE_FILE}"
migrate "${ECHO_COUPON_LINK}" "${ECHO_COUPON_FILE}"
